package classLoader;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
//package myclassloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author Daryna_Ragimova
 */

public class DynamicClassLoader extends ClassLoader {
    /**
     * Variable classesHash is usefull when user call method loadClass() directly (without calling forName())
     * Without caching two subsequent calls of loader.loadClass("Class_Name") will give you a LinkageError.
     * VM doesn't allow under the same instance of loader determine twice  the same class, i.e. refer to the method defineClass
     * The method forName provides perfect caching
     */
    private Map classesHash = new HashMap();
    
    /** 
     * @param filename
     * @return return file as a byte array by reading the entirety of the file from disk
     * @throws IOException 
     */
    private byte[] getBytes( String filename ) throws IOException {
        // Find out the length of the file
        File file = new File( filename );
        long len = file.length();
        // Create an array that's just the right size for the file's
        // contents
        byte raw[] = new byte[(int)len];
        // Open the file
        FileInputStream fin = new FileInputStream( file );
        // Read all of it into the array; if we don't get all,
        // then it's an error.
        int r = fin.read( raw );
        if (r != len)
            throw new IOException( "Can't read all, "+r+" != "+len );
        // Don't forget to close the file!
        fin.close();
        // And finally return the file contents as an array
        return raw;
    }
   
    /**
     * Spawn a process to compile the java source code file 
     * specified in the 'javaFile' parameter.
     * @param javaFile
     * @return true if the compilation worked, false otherwise.
     * @throws IOException 
     */
    private boolean compile( String javaFile ) throws IOException {
        // Let the user know what's going on
        System.out.println( "Compiling "+javaFile+"..." );
        // Start up the compiler
        Process p = Runtime.getRuntime().exec( "javac "+javaFile );
        // Wait for it to finish running
        try {
            p.waitFor();
        } catch( InterruptedException ie ) { System.out.println( ie ); }
        // Check the return code, in case of a compilation error
        int ret = p.exitValue();
        // Tell whether the compilation worked
        return ret==0;
    }
    
    @Override
    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class result = findClass(name);
        if (resolve) {
            resolveClass(result);
        }
        return result;
    }
    
       
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        //Class result = findLoadedClass( name );
        Class result = (Class)classesHash.get(name);
        if (result != null) {
            System.out.println("% Class "+name+" found in cache");
            return result;
        }

        String fileStub = name.replace( '.', '/' ) ;
        // Build objects pointing to the source code (.java) and object
        // code (.class)
        String javaFilename =  fileStub+".java";
        String classFilename = fileStub+".class";
        
        File classFile = new File( classFilename );
        File javaFile = new File( javaFilename );
        // First, see if we want to try compiling. We do if (a) there
        // is source code, and either (b0) there is no object code,
        // or (b1) there is object code, but it's older than the source
        if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) {
            try {
                
                // Try to compile it. If this doesn't work, then
                // we must declare failure. (It's not good enough to use
                // and already-existing, but out-of-date, classfile)
                if (!compile( javaFilename ) || !classFile.exists()) {
                        throw new ClassNotFoundException( "Compile failed: "+javaFilename );
                }
            } catch( IOException ie ) {
                // Another place where we might come to if we fail
                // to compile
                    throw new ClassNotFoundException( ie.toString() );
            }
        }
        
        // Let's try to load up the raw bytes, assuming they were
        // properly compiled, or didn't need to be compiled
        try {
            // read the bytes
            byte raw[] = getBytes( classFilename );
            // try to turn them into a class
            result= defineClass( name, raw, 0, raw.length );
        } catch( IOException ie ) {
            // This is not a failure! If we reach here, it might
            // mean that we are dealing with a class in a library,
            // such as java.lang.Object
        }

        // Maybe the class is in a library -- try loading
        // the normal way
        if (result == null) {
            result = findSystemClass( name );
        }
         classesHash.put(name,result);
         return result;
         }

}
